package com.tjetc.service.impl;


import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.SerializeUtil;
import cn.hutool.json.JSONUtil;
import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper;
import com.tjetc.dao.TicketMapper;
import com.tjetc.dao.OrdersMapper;
import com.tjetc.dao.PassMapper;
import com.tjetc.domain.dto.OrderDTO;
import com.tjetc.domain.enums.OrderStatus;
import com.tjetc.domain.po.Orders;
import com.tjetc.domain.po.Ticket;
import com.tjetc.domain.vo.BackVO;
import com.tjetc.domain.vo.OrderVo;
import com.tjetc.domain.vo.PassVo;
import com.tjetc.listerner.DelayMessageProcessor;
import com.tjetc.service.IOrdersService;
import com.tjetc.utils.*;
import org.springframework.amqp.AmqpException;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageBuilder;
import org.springframework.amqp.core.MessagePostProcessor;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;

/**
 * 订单管理Service业务层处理
 *
 * @author ruoyi
 * @date 2024-07-09
 */
@Service
public class OrdersServiceImpl implements IOrdersService {
    @Autowired
    private OrdersMapper ordersMapper;

    @Autowired
    private PassMapper passMapper;

    @Autowired
    private PoToVoUtil poToVoUtil;

    @Autowired
    private TicketMapper ticketMapper;

    @Autowired
    private RedisIdWorker redisIdWorker;

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @Autowired
    private DistributedLockFactory distributedLockFactory;


    @Autowired
    private RabbitTemplate rabbitTemplate;


    @Value("${server.port}")
    private String port;

    private final String lock = "pass:count:district:";

    private final String passLock = "pass:district:";


    public static final String EXCHANGE_DIRECT = "exchange.direct.order";
    public static final String ROUTING_KEY = "order";
    public static final String QUEUE_NAME = "simple.queue";


    @Transactional
    @Override
    public Long insertOrders(OrderDTO orderDTO) {
        // 获取当前passId的剩余座位数
        String value = stringRedisTemplate.opsForValue().get(lock + orderDTO.getPassId());
        Integer count = Integer.valueOf(value);
        if (count > 0) {
            // 设置setnx分布式锁
            stringRedisTemplate.opsForValue().setIfAbsent(passLock + orderDTO.getPassId(), "true", 5, TimeUnit.SECONDS);
            //添加订单
            orderDTO.setStatus(OrderStatus.getOrderStatus("未支付").getNum());
            orderDTO.setOrderNo(redisIdWorker.nextId("订单编号"));
            orderDTO.setCreateTime(LocalDateTime.now());
            int orderId = ordersMapper.insertOrders(orderDTO);
            count -= orderDTO.getNum();
            stringRedisTemplate.opsForValue().set(lock + orderDTO.getPassId(), count.toString());
            //扣减座位数量
            passMapper.reduceCount(orderDTO.getNum(), orderDTO.getPassId());
            //添加演唱会票
            for (Integer i = 0; i < orderDTO.getNum(); i++) {
                ticketMapper.add(new Ticket(orderDTO.getPassId(), orderDTO.getId()));
            }
            if (Objects.isNull(stringRedisTemplate.opsForValue().get(passLock + orderDTO.getPassId()))) {
                stringRedisTemplate.delete(passLock + orderDTO.getPassId());
            }
            return orderDTO.getOrderNo();
        }
        return 0L;
    }


    @Override
    public Orders selectByOrderNo(String orderNo) {
        return ordersMapper.selectByOrderNo(orderNo);
    }

    @Override
    public void updateById(Orders orders) {
        ordersMapper.updateById(orders);
    }

    @Override
    public List<Orders> findUnpaidOrdersOver30Minutes() {
        Integer time = 2;
        Integer status = 1;
        return ordersMapper.findUnpaidOrdersOver30Minutes(2, 1);
    }


    @Override
    public String cancelOrder(OrderVo orderVo) {


        if (!orderVo.getStatus().equals(OrderStatus.getOrderStatus("未支付").getNum())) {
            return "订单状态不是未支付,取消失败";
        }
        BackVO backVO = ordersMapper.findStartTime(orderVo.getOrderNo().toString());
        if (LocalDateTime.now().isAfter(backVO.getStartTime())) {
            return "演唱会已经开始了，取消失败";
        }
        if (Duration.between(LocalDateTime.now(), orderVo.getStartTime()).toHours() < 2) {
            return "离演唱会开始不足两小时，取消失败";
        }
        /*增加缓存库存*/
        // stringRedisTemplate.opsForValue().set("pass:"+orderVo.getPassId(), count+orderVo.getNum().toString());
        /*增加数据库库存*/
        System.out.println(orderVo.getNum());
        System.out.println(orderVo.getNum());
        passMapper.updateCountByPassId(orderVo.getNum(), orderVo.getPassId());
        /*更改订单状态*/
        orderVo.setStatus(OrderStatus.getOrderStatus("已取消").getNum());
        ordersMapper.cancelOrder(orderVo);
        /*删票*/
        ticketMapper.delByOrderId(orderVo.getId(), 1);
        return "取消成功";
    }

    @Override
    public String backMoney(Orders orders) {
        OrderVo orderVo = poToVoUtil.toOrderVo(orders);
        /*增加缓存库存*/
        // stringRedisTemplate.opsForValue().set("pass:"+orderVo.getPassId(), count+orderVo.getNum().toString());
        /*增加数据库库存*/
        passMapper.updateCountByPassId(orderVo.getNum(), orderVo.getPassId());
        /*更改订单状态*/
        orderVo.setStatus(OrderStatus.BACK.getNum());
        ordersMapper.backMoney(orderVo);
        /*删票*/
        ticketMapper.delByOrderId(orderVo.getId(), 1);
        return "退款成功";
    }

    @Override
    public BackVO findStartTime(String orderNo) {
        BackVO backVO = ordersMapper.findStartTime(orderNo);
        // System.out.println(backVO.getStartTime());
        return backVO;
    }

    @Override
    public PageBean<OrderVo> list(Integer pageNum, Integer pageSize, String status, String startTime, String endTime, Integer userId) {
        //1.创建PageBean对象
        PageBean<OrderVo> pb = new PageBean<>();
        //2.开启分页查询
        PageHelper.startPage(pageNum, pageSize);
        List<OrderVo> ordersList = ordersMapper.list(userId, status, startTime, endTime);
        Page<OrderVo> p = (Page<OrderVo>) ordersList;
        pb.setTotal(p.getTotal());
        pb.setItems(p.getResult());
        return pb;
    }

    @Override
    public Orders findById(Integer id) {
        return ordersMapper.findById(id);
    }


    @Override
    public void finishOrder(Orders order) {
        order.setStatus(OrderStatus.FINALLY.getNum());
        ordersMapper.finishOrder(order);
    }

    @Override
    public List<Orders> findPaidOrdersOverStart() {
        Integer hour = 2;
        Integer status = 2;
        return ordersMapper.findPaidOrdersOverStart(hour, status);
    }


    /*
     * V8.0  实现自动续期功能的完善,后台自定义扫描程序，如果规定时间内没有完成业务逻辑，会调用加钟自动续期的脚本
     * */
    @Override
    public Long sale(OrderDTO orderDTO) {
        if (!BloomFilterInit.bloomFilter.mightContain(orderDTO.getPassId())) {
            //布隆过滤器里没有，一定没有
            return 0L;
        }
        Lock redisLock = distributedLockFactory.getDistributedLock("redis");
        redisLock.lock();
        try {
            //查询原来的库存
            var result = stringRedisTemplate.opsForValue().get("pass:" + orderDTO.getPassId());
            if (ObjectUtil.isNull(result)) {
                result = stringRedisTemplate.opsForValue().get("pass2:" + orderDTO.getPassId());
            }
            if (ObjectUtil.isNull(result)) {
                PassVo pass = passMapper.findById(orderDTO.getPassId());
                if (ObjectUtil.isNotNull(pass)) {
                    return 0L;
                }
                Integer count = pass.getCount();
            }

            Integer count = result == null ? 0 : Integer.parseInt(result);
            //扣减库存
            if (count - orderDTO.getNum() >= 0) {
                orderDTO.setOrderNo(redisIdWorker.nextId("订单编号"));
                rabbitTemplate.convertAndSend("queue.order", SerializeUtil.serialize(orderDTO));

              /*  orderDTO.setStatus(OrderStatus.getOrderStatus("未支付").getNum());
                orderDTO.setOrderNo(redisIdWorker.nextId("订单编号"));
                orderDTO.setCreateTime(LocalDateTime.now());
                ordersMapper.insertOrders(orderDTO);
                *//*扣减缓存座位数量*//*
                //stringRedisTemplate.opsForValue().set("pass:1", String.valueOf(count - orderDTO.getNum()));
                //扣减数据库座位数量
                passMapper.reduceCount(orderDTO.getNum(), orderDTO.getPassId());
                //添加演唱会票
                for (Integer i = 0; i < orderDTO.getNum(); i++) {
                    ticketMapper.add(new Ticket(orderDTO.getPassId(), orderDTO.getId()));
                }
                */
                 System.out.println("成功卖出" + orderDTO.getPassId() + "通道" + orderDTO.getNum()  + "服务端口号" + port);
                //故意的 测可重入性
                testReEntry();
                /*故意的  暂停120s  测试自动续期的功能*/
                // TimeUnit.SECONDS.sleep(120);


                //*延迟检查订单状态*//*
                //var msg = MultiDelayMessage.of(orderDTO.getOrderNo(), 10000L, 10000L, 15000L, 15000L, 1300000L);
                // rabbitTemplate.convertAndSend(EXCHANGE_DIRECT, ROUTING_KEY, msg, new DelayMessageProcessor(msg.removeNextDelay()));

                rabbitTemplate.convertAndSend(EXCHANGE_DIRECT, ROUTING_KEY, orderDTO.getOrderNo().toString(), new MessagePostProcessor() {
                    @Override
                    public Message postProcessMessage(Message message) throws AmqpException {
                        message.getMessageProperties().setExpiration("1800000");
                        return message;
                    }
                });


                return orderDTO.getOrderNo();
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            redisLock.unlock();
        }
        return 0L;
    }

    private void testReEntry() {
        Lock redisLock = distributedLockFactory.getDistributedLock("redis");
        redisLock.lock();
        try {
            //    System .out.println("-------测试可重入锁--------");
        } finally {
            redisLock.unlock();
        }









        /*V7 如何将我们的lock /unlock +lua脚本自研版本的redis分布式锁搞定？
         *
         * */
/*    @Override
    public String sale(OrderDTO orderDTO) {
        String retMessage = "";
        Lock redisLock = distributedLockFactory.getDistributedLock("redis");
        redisLock.lock();
        try {
            //1.查询原来的库存
            var result = stringRedisTemplate.opsForValue().get("pass:" + orderDTO.getPassId());
            Integer count = result == null ? 0 : Integer.parseInt(result);
            //3.扣减库存
            if (count - orderDTO.getNum() >= 0) {
                stringRedisTemplate.opsForValue().set("pass:1", String.valueOf(count - orderDTO.getNum()));
                orderDTO.setStatus(OrderStatus.getOrderStatus("未支付").getNum());
                orderDTO.setOrderNo(redisIdWorker.nextId("订单编号"));
                orderDTO.setCreateTime(LocalDateTime.now());
                ordersMapper.insertOrders(orderDTO);
                //扣减座位数量
                passMapper.reduceCount(orderDTO.getNum(), orderDTO.getPassId());
                //添加演唱会票
                for (Integer i = 0; i < orderDTO.getNum(); i++) {
                    ticketMapper.add(new Ticket(orderDTO.getPassId(), orderDTO.getId()));
                }
                retMessage = "成功卖出" + orderDTO.getPassId() + "通道" + orderDTO.getNum() + "个商品，库存余额:" + (count - orderDTO.getNum());
                System.out.println(retMessage + "\t" + "服务端口号" + port);
                //故意的 测可重入性
                testReEntry();
            } else {
                retMessage = orderDTO.getPassId() + "通道库存不足";
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            redisLock.unlock();
        }
        return retMessage + "\t" + "服务端口号" + port;
    }

    private void testReEntry() {
        Lock redisLock = distributedLockFactory.getDistributedLock("redis");
        redisLock.lock();
        try {
            System.out.println("-------测试可重入锁--------");
        } finally {
            redisLock.unlock();

        }

    }*/

/*
加锁的lua脚本，对标lock方法
        第一版
        if redis.call('exists','key') == 0 then
            redis.call('hset','key',uuid:threadId,1)
            redis.call('expire','key',50)
            return 1
        elseif  redis.call('hexists','key','uuid:threadid') then  //判断当前field是否存在 ，就是锁是不是自己的
            redis.call('hincrby','key','uuid:threadid',1)  是自己的，计数器+1
            redis.call('expire','key',50)   过期时间多续一会
            return 1
        else
            return 0
        end
       问题： if  和  elseif里 只有 'hset' 和 'hincrby' 不一样 ，能不能 hincrby 替换 hset  然后  if和 elseif 合二为一
       答：可以
       比如  hincrby  k1  f1  v1
             hget    k1  f1  能拿到v1，不需要hset

     第二版 合并相同的代码 hincr替代hset
          if redis.call('exists','key') == 0   or   redis.call('hexists','key','uuid:threadid')==1   then
            redis.call('hincrby','key',uuid:threadId,1)
            redis.call('expire','key',50)
            return 1
        else
            return 0
        end

    第三版： 脚本ok 换上参数

     if redis.call('exists', KEYS[1]) == 0   or   redis.call('hexists', KEYS[1]  ,ARGV[1] )==1   then redis.call('hincrby',KEYS[1], ARGV[1] ,1)  redis.call('expire', KEYS[1] , ARGV[2]) return 1   else  return 0  end

      EVAL "if redis.call('exists', KEYS[1]) == 0   or   redis.call('hexists', KEYS[1]  ,ARGV[1] )==1   then redis.call('hincrby',KEYS[1], ARGV[1] ,1)  redis.call('expire', KEYS[1] , ARGV[2]) return 1   else  return 0  end "  1 lmc lmcfield 50


解锁的lua脚本 对标unlock方法

    版本1
    if redis.call('hexists',key,uuid:threadId) == 0 then   //判断当前field是否存在 ，就是锁是不是自己的
        return nil
    elseif  redis.call('hincrby',key,uuid:threadid,-1) == 0 then
        return redis.call('del',key)
   else
    return 0
    end

    版本2
   EVAL   "if redis.call('hexists',KEYS[1],ARGV[1]) == 0 then     return nil elseif  redis.call('hincrby',KEYS[1],ARGV[1],-1) == 0 then          return redis.call('del',KEYS[1])  else   return 0      end" 1 lmc lmcfield
 */

        /*V7.0
         * 可重入锁
         *  什么是锁的可重入性： 同一个线程，如果 执行的外部方法和内部方法里都需要获取同一把锁(锁对象是同一个对象),线程不会阻塞在内部的方法上 ： 简称 自己可以获得自己的内部锁
         *   synchronize的和 ReentrantLock 都是 可重入的 ，一定程度避免了死锁
         *  synchronize 实现可重入锁的原理是：每个锁对象都拥有一个锁计数器和执行持有该锁线程的指针，当执行monitorenter就是进去的时候，如果目标锁对象的计数器为0,说明没有被其它线程持有，jvm会将该锁对象
         * 的持有线程设置为当前线程，就是指针指向这个线程 ，每个对象身上其实都有计数器，然后计数器+1。再次执行monitorenter就不是0咯，就判断当前线程是不是指针指向的，是的话就计数器+1，不用等待
         * 指针指向的线程和当前线程不是同一个 的话，当前线程就会阻塞，知道持有锁的线程释放资源
         *  执行monitorexit时，计数器-1，如果计数器的值为0，代表锁被释放了
         * ReentryLock显示锁原理也差不多
         * 我们需要 锁的key  lock:pass:1
         *         锁的值   uuid:threadID
         *          计数器   记录线程进入几次
         * 典型的 k k  v
         * setnx 不行了  必须用  hash结构
         * 线程第一次进来  hset 锁的key  锁的值   1
         * 第二次进来     hincrby  锁的key   锁的值  1
         * 进去几次就要释放几次
         * hincrby 锁的key 锁的vlue  -1
         * hincrby 锁的key 锁的vlue  -1
         * 现在计数器的值是0了
         * del  锁的key
         * */

        /*
         * 先判断有没有redis分布式锁
         * 有看看是不是自己
         * 是自己的+1
         * 解锁 -1
         * 删掉分布式锁
         * 必须全用lua脚本，因为加锁的一大段 ， 减锁的一大段，删锁的一大段 必须有if,else各种判断
         * */


  /*
    线程能进最里面证明了 synchronize是可重入的 ,否则它将卡死在第二把锁上 （同步方法也是可重入的，不证明了）
     final Object obj = new Object();
        public  void entry01() {
            new Thread(()->{
                synchronized (obj){
                    synchronized (obj){
                        synchronized (obj){
                            System.out.println("我进来了");
                        }
                    }
                }
            },"t1").start();
        }


   */
  /*
  ReentrantLock也是可重入的
       注意： 加几次所，释放几次锁 否则 看下面的
    Lock lock2 = new ReentrantLock();
    public void entry02() {
        new Thread(() -> {
            lock2.lock();
            lock2.lock();
            System.out.println("我进来了");
            lock2.unlock();//unlock不写finally里了, 影响观感
            lock2.unlock();
            // sleep 一会 让 t2启动
        }, "t1").start();

        new Thread(() -> {
            lock2.lock();
            System.out.println("我不受上面的影响");
            lock2.unlock();
        }, "t2").start();
    }
*/
     /*

        t1线程加两次锁，但制释放一次，t1能正常执行，但t1会祸害t2和其它想要获得同一把锁的线程
        t1加两次锁，释放1次， t1跑了 ，但lock2身上还记着一笔账呢，lock2自认为还有人持有我，我没有办法放，所以会导致，后面的t2永远得不到lock2 ，卡死了
    Lock lock2 = new ReentrantLock();
    public void entry02() {
        new Thread(() -> {
            lock2.lock();
            lock2.lock();
            System.out.println("我进来了");
            lock2.unlock(); //我制释放一次嘿嘿

            // sleep 一会 让 t2启动
        }, "t1").start();

        new Thread(() -> {
             System.out.println("我卡死这里了，t1害人精");
            lock2.lock();
            lock2.unlock();
        }, "t2").start();
    }
*/
        /*V6.0目前是 while判断并自旋重试获取锁+setnx含过期时间 + lua脚本删除锁
         *   问题：如何兼顾锁的可重入性问题？ 大厂必问
         *
         *   一个满足AQS(一种编写锁的规范 Abstract  Queue  synchronize  ) 的分布式锁的刚需
         * 1.独占锁
         * 2.高可用
         * 3.防死锁： 锁加过期时间，A线程的订单模块宕机了,redis锁到期自动消失,不影响其它订单模块
         * 4.不乱抢： lua脚本说话
         * 5.可重入
         * */
    /*V6.0  lua脚本保证判断锁是不是自己的和删除锁的 原子性  【 Redis官网要求的 】
               什么是脚本： 比如 linux里的 shell脚本（把一些linux命令，用一些编程语言这里是shell编程 整合进去，实现复杂功能）就是指定了一套语言规约，目的是嵌入一些linux命令实现复杂功能
                           复杂功能是什么： 某些业务场景下，我的命令需要加判定条件才能执行，1号条件执行1号命令 ，2号条件执行4号，5号，6号 命令，以及需要对命令动态传参，循环等
                          再比如：html 里 加 <%=...%>  就是jsp脚本 让页面静态数据 可以 实现 判断 是否渲染，循环 等复杂功能
               什么是lua脚本： c语言编写的，目的是通过嵌入一些我们的redis命令 实现操作Redis的复杂功能 ，比如eval开头就能能保证多条redis命令的原子性,再比如满足1号条件，执行 1号redis命令...
               【Redis官网的原文  解决处理判断锁是不是自己的和删除锁不满足原子性的 lua脚本  】
               if  redis.call("get",KEYS[1]) == ARGV[1]     then
                   return redis.call("del", KEYS[1])
               else
                   return 0
               end

               lua语法：
                 案例1:打印 hello lua
                   EVAL "return 'hello lua' "   0   注意： 0是因为没有参数
                 案例2:把 set k1 v1  和 expire k1 30 和 get k1  三合一      注意：三条命令都有返回值    return 写第三条命令前，返回的是三条命令都执行过后，最后一条命令的结果 , return直接写第一条命令前会报错，因为三条都有返回值，return只能返回一个，如果就是三个返回值都需要 那要加if了
                    EVAL "redis.call('set','k1','v1')   redis.call('expire', 'k1', '30')  return  redis.call('get','k1')"  0
                案例3：实现 mset k1 v1 k2 v2  , 其中v1 ,k1 等要动态传参
                   EVAL "return redis.call('mset','k1','v1','k2','v2')" 0
                   EVAL "return redis.call('mset','KEYS[1]','ARGV[1]','KEYS[2]','ARGV[2]')"  2 k1 k2 lua1  lua2  注意：2就是key的个数 , k1会替换KEYS[1] ，k2替换[KEYS2] ， lua1替换[ARGV[1]],lua2替换[ARGV[2]]  下标都是从1开始
                案例4：实现判断
                    语法： if(条件，小括号可以省)  then
                         业务代码
                          elseif(条件)   then
                          业务代码
                          elseif(条件)   then
                          业务代码
                          else    end
                    例子： if 5 > 7 then
                           return 1
                          elseif 5< 7 then
                           return  2
                          else return 3
                          end

                      EVAL   "if KEYS[1] >KEYS[2] then
                           return [ARGV1]
                          elseif KEYS[1] < KEYS[2] then
                           return  [ARGV2]
                          else return [ARGV3]
                          end"  2 5 7  1 2 3
             案例4：手写官网的脚本
              注意 ： EVAL "if redis.call("get",KEYS[1])  == ARGV[1] then return redis.call("del",KEYS[1] ) else return 0 end"  1  锁的key   锁的value  )) 错的
             外面有双引号， 里面需要单引号  EVAL "if redis.call('get',KEYS[1])  == ARGV[1] then return redis.call('del',KEYS[1] ) else return 0 end"  1  锁的key   锁的value  ))


     */
     /*  public String sale(OrderDTO orderDTO) {
        String retMessage = "";
        String key = "lock:pass:" + orderDTO.getPassId();
        String uuidValue = IdUtil.simpleUUID() + ":" + Thread.currentThread().getId();
        //自旋替代递归实现重试  ， 也不用if了  用while来替代
        while (!stringRedisTemplate.opsForValue().setIfAbsent(key, uuidValue, 30L, TimeUnit.SECONDS)) {
            //暂停20ms进行递归重试
            try {
                TimeUnit.MILLISECONDS.sleep(20);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        //通过了
        try {
            //1.查询原来的库存
            var result = stringRedisTemplate.opsForValue().get("pass:" + orderDTO.getPassId());
            Integer count = result == null ? 0 : Integer.parseInt(result);
            //3.扣减库存
            if (count - orderDTO.getNum() >= 0) {
                stringRedisTemplate.opsForValue().set("pass:1", String.valueOf(count - orderDTO.getNum()));
                orderDTO.setStatus(OrderStatus.getOrderStatus("未支付").getNum());
                orderDTO.setOrderNo(redisIdWorker.nextId("订单编号"));
                orderDTO.setCreateTime(LocalDateTime.now());
                ordersMapper.insertOrders(orderDTO);
                //扣减座位数量
                passMapper.reduceCount(orderDTO.getNum(), orderDTO.getPassId());
                //添加演唱会票
                for (Integer i = 0; i < orderDTO.getNum(); i++) {
                    ticketMapper.add(new Ticket(orderDTO.getPassId(), orderDTO.getId()));
                }
                retMessage = "成功卖出" + orderDTO.getPassId() + "通道" + orderDTO.getNum() + "个商品，库存余额:" + (count - orderDTO.getNum());
                System.out.println(retMessage + "\t" + "服务端口号" + port);
            } else {
                retMessage = orderDTO.getPassId() + "通道库存不足";
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //修改为lua脚本
            String luaScript ="if redis.call('get',KEYS[1])  == ARGV[1] then " +
                    "return redis.call('del',KEYS[1] ) else return 0 end";
                    stringRedisTemplate.execute(new DefaultRedisScript<>(luaScript,Boolean.class), Arrays.asList(key), uuidValue);
        }
        return retMessage + "\t" + "服务端口号" + port;
    }*/


        /*V5.0   删锁前先get(key)判断value是否相等  解决误删问题
         *问题：
         *   判断锁是不是自己的和删除锁 是两条语句： 如果判断进来了，但还没删 出现线程切换，宕机就有问题了
         * */
  /* public String sale(OrderDTO orderDTO) {
        String retMessage = "";
        String key = "lock:pass:" + orderDTO.getPassId();
        String uuidValue = IdUtil.simpleUUID() + ":" + Thread.currentThread().getId();
        //自旋替代递归实现重试  ， 也不用if了  用while来替代
        while (!stringRedisTemplate.opsForValue().setIfAbsent(key, uuidValue, 30L, TimeUnit.SECONDS)) {
            //暂停20ms进行递归重试
            try {
                TimeUnit.MILLISECONDS.sleep(20);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        //通过了
        try {
            //1.查询原来的库存
            var result = stringRedisTemplate.opsForValue().get("pass:" + orderDTO.getPassId());
            Integer count = result == null ? 0 : Integer.parseInt(result);
            //3.扣减库存
            if (count - orderDTO.getNum() >= 0) {
                stringRedisTemplate.opsForValue().set("pass:1", String.valueOf(count - orderDTO.getNum()));
                orderDTO.setStatus(OrderStatus.getOrderStatus("未支付").getNum());
                orderDTO.setOrderNo(redisIdWorker.nextId("订单编号"));
                orderDTO.setCreateTime(LocalDateTime.now());
                ordersMapper.insertOrders(orderDTO);
                //扣减座位数量
                passMapper.reduceCount(orderDTO.getNum(), orderDTO.getPassId());
                //添加演唱会票
                for (Integer i = 0; i < orderDTO.getNum(); i++) {
                    ticketMapper.add(new Ticket(orderDTO.getPassId(), orderDTO.getId()));
                }
                retMessage = "成功卖出" + orderDTO.getPassId() + "通道" + orderDTO.getNum() + "个商品，库存余额:" + (count - orderDTO.getNum());
                System.out.println(retMessage + "\t" + "服务端口号" + port);
            } else {
                retMessage = orderDTO.getPassId() + "通道库存不足";
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (stringRedisTemplate.opsForValue().get(key).equalsIgnoreCase(uuidValue)) {
                stringRedisTemplate.delete(key);
            }
        }
        return retMessage + "\t" + "服务端口号" + port;
    }*/


        /*V4.0 加锁时加过期时间 解决锁可能删不掉的问题
         * 注意：加锁和设置过期时间必须是一条语句完成 保证原子性
         *   否则：两条语句间只要有缝隙，高并发 在缝隙处就可能线程切换，或者宕机等
         * 问题：可能误删别人锁
         *   正常情况下：比如锁是30s失效 ，业务时间10s就完了，A线程自己建锁，A自己删， 然后B进来创建，B自己删 很和谐
         *   但特殊情况下：比如锁是30s失效，业务A线程1分钟才执行完，在第30s和1分钟之间  在外面自旋的B线程就有可能 创建锁成功而且key和A创建的一样(代码是这样的）
         *   1分钟时，A业务执行完了，执行delete锁，就会删掉B创建的锁，因为key都一样叫 lock:pass:1 ,B执行完就懵逼了，我的锁呢？
         * */
    /*public String sale(OrderDTO orderDTO) {
        String retMessage = "";
        String key = "lock:pass:" + orderDTO.getPassId();
        String uuidValue = IdUtil.simpleUUID() + ":" + Thread.currentThread().getId();
        //自旋替代递归实现重试  ， 也不用if了  用while来替代
        while (!stringRedisTemplate.opsForValue().setIfAbsent(key, uuidValue,30L,TimeUnit.SECONDS)){
            //暂停20ms进行递归重试
            try {
                TimeUnit.MILLISECONDS.sleep(20);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        //通过了
        try {
            //1.查询原来的库存
            var result = stringRedisTemplate.opsForValue().get("pass:" + orderDTO.getPassId());
            Integer count = result == null ? 0 : Integer.parseInt(result);
            //3.扣减库存
            if (count - orderDTO.getNum() >= 0) {
                stringRedisTemplate.opsForValue().set("pass:1", String.valueOf(count - orderDTO.getNum()));
                orderDTO.setStatus(OrderStatus.getOrderStatus("未支付").getNum());
                orderDTO.setOrderNo(redisIdWorker.nextId("订单编号"));
                orderDTO.setCreateTime(LocalDateTime.now());
                ordersMapper.insertOrders(orderDTO);
                //扣减座位数量
                passMapper.reduceCount(orderDTO.getNum(), orderDTO.getPassId());
                //添加演唱会票
                for (Integer i = 0; i < orderDTO.getNum(); i++) {
                    ticketMapper.add(new Ticket(orderDTO.getPassId(), orderDTO.getId()));
                }
                retMessage = "成功卖出" + orderDTO.getPassId() + "通道" + orderDTO.getNum() + "个商品，库存余额:" + (count - orderDTO.getNum());
                System.out.println(retMessage + "\t" + "服务端口号" + port);
            } else {
                retMessage = orderDTO.getPassId() + "通道库存不足";
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            stringRedisTemplate.delete(key);
        }*/


        /*V3.2   用自旋替代递归      用while代替if        先完成，再完美，先功能，再性能
         * 问题
         *   redis分布式锁的要加过期时间
         *   否则，当A线程创建分布式锁成功后，在释放锁前 ，A线程在的订单模块宕机了，其它能正常走的订单模块会卡死在那把A没释放的锁上
         * */
   /* public String sale(OrderDTO orderDTO) {
        String retMessage = "";
        String key = "lock:pass:" + orderDTO.getPassId();
        String uuidValue = IdUtil.simpleUUID() + ":" + Thread.currentThread().getId();
        //自旋替代递归实现重试  ， 也不用if了  用while来替代
        while (!stringRedisTemplate.opsForValue().setIfAbsent(key, uuidValue)){
            //暂停20ms进行递归重试
            try {
                TimeUnit.MILLISECONDS.sleep(20);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
     //通过了
        try {
            //1.查询原来的库存
            var result = stringRedisTemplate.opsForValue().get("pass:" + orderDTO.getPassId());
            Integer count = result == null ? 0 : Integer.parseInt(result);
            //3.扣减库存
            if (count - orderDTO.getNum() >= 0) {
                stringRedisTemplate.opsForValue().set("pass:1", String.valueOf(count - orderDTO.getNum()));
                orderDTO.setStatus(OrderStatus.getOrderStatus("未支付").getNum());
                orderDTO.setOrderNo(redisIdWorker.nextId("订单编号"));
                orderDTO.setCreateTime(LocalDateTime.now());
                ordersMapper.insertOrders(orderDTO);
                //扣减座位数量
                passMapper.reduceCount(orderDTO.getNum(), orderDTO.getPassId());
                //添加演唱会票
                for (Integer i = 0; i < orderDTO.getNum(); i++) {
                    ticketMapper.add(new Ticket(orderDTO.getPassId(), orderDTO.getId()));
                }
                retMessage = "成功卖出" + orderDTO.getPassId() + "通道" + orderDTO.getNum() + "个商品，库存余额:" + (count - orderDTO.getNum());
                System.out.println(retMessage + "\t" + "服务端口号" + port);
            } else {
                retMessage = orderDTO.getPassId() + "通道库存不足";
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            stringRedisTemplate.delete(key);
        }

        return retMessage + "\t" + "服务端口号" + port;
    }*/


    /*V3.1 递归重试解决了超卖
      问题：
    * 高并发禁止用递归
    * 高并发用if会出现虚假唤醒
    *       */
  /*  public String sale(OrderDTO orderDTO) {
        String retMessage = "";
        String key = "lock:pass:" + orderDTO.getPassId();
        String uuidValue = IdUtil.simpleUUID() + ":" + Thread.currentThread().getId();
        Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(key, uuidValue);
        //抢不到的线程要继续重试
        if (!flag) {
            //暂停20ms进行递归重试
            try {
                TimeUnit.MILLISECONDS.sleep(20);
                sale(orderDTO);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        } else {
            //抢锁成功的请求
            try {
                //1.查询原来的库存
                var result = stringRedisTemplate.opsForValue().get("pass:" + orderDTO.getPassId());
                Integer count = result == null ? 0 : Integer.parseInt(result);
                //3.扣减库存
                if (count - orderDTO.getNum() >= 0) {
                    stringRedisTemplate.opsForValue().set("pass:1", String.valueOf(count - orderDTO.getNum()));
                    orderDTO.setStatus(OrderStatus.getOrderStatus("未支付").getNum());
                    orderDTO.setOrderNo(redisIdWorker.nextId("订单编号"));
                    orderDTO.setCreateTime(LocalDateTime.now());
                    ordersMapper.insertOrders(orderDTO);
                    //扣减座位数量
                    passMapper.reduceCount(orderDTO.getNum(), orderDTO.getPassId());
                    //添加演唱会票
                    for (Integer i = 0; i < orderDTO.getNum(); i++) {
                        ticketMapper.add(new Ticket(orderDTO.getPassId(), orderDTO.getId()));
                    }
                    retMessage = "成功卖出" + orderDTO.getPassId() + "通道" + orderDTO.getNum() + "个商品，库存余额:" + (count - orderDTO.getNum());
                    System.out.println(retMessage + "\t" + "服务端口号" + port);
                } else {
                    retMessage = orderDTO.getPassId() + "通道库存不足";
                }
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                stringRedisTemplate.delete(key);
            }
        }
        return retMessage + "\t" + "服务端口号" + port;
    }*/


    /*V2.0   单机版加锁配合Nginx和 Jmeter压测后 出现超卖等问题
     问题：
     不满足分布式锁的要求 */
   /* private Lock lock1 = new ReentrantLock();
    @Override
    public String sale(OrderDTO orderDTO) {
        String retMessage = "";
        lock1.lock();
        try {
            //1.查询原来的库存
            var result = stringRedisTemplate.opsForValue().get("pass:"+orderDTO.getPassId());
            Integer count = result == null ? 0 : Integer.parseInt(result);
            //3.扣减库存
            if (count - orderDTO.getNum() >= 0) {
                stringRedisTemplate.opsForValue().set("pass:1", String.valueOf(count - orderDTO.getNum()));
                orderDTO.setStatus(OrderStatus.getOrderStatus("未支付").getNum());
                orderDTO.setOrderNo(redisIdWorker.nextId("订单编号"));
                orderDTO.setCreateTime(LocalDateTime.now());
                ordersMapper.insertOrders(orderDTO);
                //扣减座位数量
                passMapper.reduceCount(orderDTO.getNum(), orderDTO.getPassId());
                //添加演唱会票
                for (Integer i = 0; i < orderDTO.getNum(); i++) {
                    ticketMapper.add(new Ticket(orderDTO.getPassId(), orderDTO.getId()));
                }
                retMessage = "成功卖出"+orderDTO.getPassId()+"通道"+orderDTO.getNum()+"个商品，库存余额:"+ (count -orderDTO.getNum());
                System.out.println(retMessage+"\t"+"服务端口号"+port);
            }else {
                retMessage =orderDTO.getPassId()+ "通道库存不足";
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock1.unlock();
        }
            return retMessage+"\t"+"服务端口号"+port;
    }*/
    }
}
